﻿using System;
using System.Collections.Generic;
/*************************************************************************
 *     This file & class is part of the Object-Oriented Optimization
 *     Toolbox (or OOOT) Project
 *     Copyright 2010 Matthew Ira Campbell, PhD.
 *
 *     OOOT is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *  
 *     OOOT is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *  
 *     You should have received a copy of the GNU General Public License
 *     along with OOOT.  If not, see <http://www.gnu.org/licenses/>.
 *     
 *     Please find further details and contact information on OOOT
 *     at http://ooot.codeplex.com/.
 *************************************************************************/
using OptimizationToolbox;
using StarMathLib;

namespace GearSynth.Evaluation
{
    /* Here is an example of a dependent analysis. Other than the constructor
     * it has but one function - calculate. Unlike objective functions and 
     * constraints, however; this returns nothing. All that it does is set all
     * of its internal properties for perusal by the other functions. */
    internal class GearTrainAnalysis : IDependentAnalysis
    {
        /* these are universally known from the user defined goals - store internally
         * for access in "calculate". */
        private readonly double[,] inputPosition;
        private readonly double inputSpeed;
        private readonly double outputTorque;
        private readonly double inputTorque;

        /* these are dependent on the graph/configuration - create as part of 
         * constructor, but make internal for access from other functions
         * (f's, g's, h's).  */
        internal readonly int numGears;
        internal readonly Role[] gearRoles;
        internal readonly GearType[] gearTypes;
        internal readonly double[] densities;
        internal readonly double[] Sfb;
        internal readonly double[] Sfc;

        /* these are dependent on the variable values - set as part of "calculate"
         * and accessed from other functions (f's, g's, h's).  */
        internal double[][,] positions { get; private set; }
        internal double[] diameters { get; private set; }
        internal double[] forces { get; private set; }
        internal double[] torques { get; private set; }
        internal double[] speeds { get; private set; }

        internal GearTrainAnalysis(List<Role> gearRoles, userDefinedGoals udg, int numGears, GearFamily[] gearFamilies)
        {
            outputTorque = udg.outputTorque;
            inputTorque = udg.inputTorque;
            inputSpeed = Math.Abs(udg.inputSpeed) * (int)udg.inputDirection;
            inputPosition = udg.inputLocation;

            this.numGears = numGears;
            this.gearRoles = new Role[numGears];
            gearTypes = new GearType[numGears];
            densities = new double[numGears];
            Sfb = new double[numGears];
            Sfc = new double[numGears];
            for (int i = 0; i < numGears; i++)
            {
                this.gearRoles[i] = gearRoles[i];
                gearTypes[i] = gearFamilies[i].gearType;
                densities[i] = gearFamilies[i].density;
                Sfb[i] = gearFamilies[i].Sfb;
                Sfc[i] = gearFamilies[i].Sfc;
            }

            diameters = new double[numGears];
            forces = new double[numGears];
            torques = new double[numGears];
            speeds = new double[numGears];
            positions = new double[numGears][,];
        }

        #region Implementation of IDependentAnalysis

        /* there are four design variables per gear:
         * 0. number of teeth (N)
         * 1. pitch (P) or module (m)...gear tooth size
         * 2. face width (F)
         * 3. location variable, z
         * by setting the NumGearPairs to 3, we are create
         * 3 * (2* 4) = 24 variables. */

        public void calculate(double[] x)
        {
            speeds[0] = inputSpeed;
            positions[0] = StarMath.multiply(inputPosition, StarMath.Translate(0.0, 0.0, x[2] / 2));
            diameters[0] = x[0] / x[1];
            for (var i = 1; i < numGears; i++)
            {
                var N = x[i * 4];
                var P = x[i * 4 + 1];
                var F = x[i * 4 + 2];
                var Z = x[i * 4 + 3];
                diameters[i] = N / P;
                if (gearRoles[i].Equals(Role.driving))
                {
                    speeds[i] = speeds[i - 1];
                    positions[i] = StarMath.multiply(positions[i - 1], StarMath.Translate(0.0, 0.0, Z + F / 2));
                }
                else
                {
                    speeds[i] = -1.0 * (x[(i - 1) * 4] / N) * speeds[i - 1];
                    positions[i] = StarMath.multiply(positions[i - 1], StarMath.RotationZ(Z));
                    switch (gearTypes[i])
                    {
                        case GearType.spur:
                        case GearType.helical:
                            positions[i] = StarMath.multiply(positions[i],
                                                             StarMath.Translate(
                                                                 (diameters[i - 1] + diameters[i]) / 2.0,
                                                                 0.0, 0.0));
                            break;
                        case GearType.bevel:
                            positions[i] = StarMath.multiply(positions[i],
                                                             StarMath.Translate(diameters[i - 1] / 2.0, 0.0, 0.0));
                            positions[i] = StarMath.multiply(positions[i], StarMath.RotationY(-90));
                            positions[i] = StarMath.multiply(positions[i],
                                                             StarMath.Translate((diameters[i] / 2), 0.0, 0.0));
                            break;
                        case GearType.wormgear:
                            positions[i] = StarMath.multiply(positions[i],
                                                             StarMath.Translate(
                                                                 (diameters[i - 1] + diameters[i]) / 2.0,
                                                                 0.0, 0.0));
                            break;
                    }
                    positions[i] = StarMath.multiply(positions[i - 1], StarMath.Translate(0.0, 0.0, F / 2));
                }
            }
            if (outputTorque > 0)
            {
                torques[numGears - 1] = outputTorque;
                forces[numGears - 1] = 2 * outputTorque / diameters[numGears - 1];
                for (var i = numGears - 2; i >= 0; i--)
                {
                    switch (gearRoles[i])
                    {
                        case Role.driving:
                            forces[i] = forces[i + 1];
                            torques[i] = forces[i] * diameters[i] / 2;
                            break;
                        case Role.driven:
                            torques[i] = torques[i + 1];
                            forces[i] = 2 * torques[i] / diameters[i];
                            break;
                        case Role.idler:
                            forces[i] = forces[i + 1];
                            break;
                    }
                }
            }
            else if (inputTorque > 0)
            {
                torques[0] = inputTorque;
                forces[numGears - 1] = 2 * inputTorque / diameters[0];
                for (var i = 1; i < numGears; i++)
                {
                    switch (gearRoles[i])
                    {
                        case Role.driven:
                            forces[i] = forces[i - 1];
                            torques[i] = forces[i] * diameters[i] / 2;
                            break;
                        case Role.driving:
                            torques[i] = torques[i - 1];
                            forces[i] = 2 * torques[i] / diameters[i];
                            break;
                        case Role.idler:
                            forces[i] = forces[i - 1];
                            break;
                    }
                }
            }
        }
        #endregion

    }
}